package top.haijunit.common.redis;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import top.haijunit.common.core.domian.ExceptionEnum;
import top.haijunit.common.core.domian.ExceptionServer;
import top.haijunit.common.redis.domain.RedissonConstant;
import top.haijunit.common.redis.domain.RedissonLockWrapper;

import java.util.Objects;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Function;

/**
 * 【说明类功能】
 *
 * @author zhanghaijun
 * @since 2024-07-31
 */
@Slf4j
@RequiredArgsConstructor
public class RedissonService {

    private final RedissonClient redissonClient;

    /**
     * 加锁执行,默认时间配置
     *
     * @param lockKey  key
     * @param request  请求参数
     * @param consumer 业务处理
     * @param <T>      参数类型
     */
    public <T> void execute(String lockKey, T request, Consumer<T> consumer) {
        execute(RedissonLockWrapper.lockWithDefaultTime(lockKey), request, consumer);
    }

    /**
     * 加锁执行,默认时间配置
     *
     * @param lockKey  key
     * @param request  请求参数
     * @param function 业务处理
     * @param <T>      参数类型
     * @param <R>      应答类型
     * @return 处理结果
     */
    public <T, R> R execute(String lockKey, T request, Function<T, R> function) {
        return execute(RedissonLockWrapper.lockWithDefaultTime(lockKey), request, function);
    }

    /**
     * 加锁执行,默认时间配置
     *
     * @param lockKey  key
     * @param runnable 业务处理
     */
    public void execute(String lockKey, Runnable runnable) {
        execute(RedissonLockWrapper.lockWithDefaultTime(lockKey), runnable);
    }

    /**
     * 加锁执行,默认时间配置
     *
     * @param lockKey  key
     * @param callable 业务处理
     * @return 处理结果
     */
    public <T> T execute(String lockKey, Callable<T> callable) {
        return doExecute(RedissonLockWrapper.lockWithDefaultTime(lockKey), callable);
    }

    /**
     * 加锁执行,指定时间配置
     *
     * @param lockConfigBO 配置
     * @param request      请求参数
     * @param consumer     业务处理
     * @param <T>          参数类型
     */
    public <T> void execute(RedissonLockWrapper lockConfigBO, T request, Consumer<T> consumer) {
        doExecute(lockConfigBO, () -> {
            consumer.accept(request);
            return RedissonConstant.SUCCESS;
        });
    }

    /**
     * 加锁执行,指定时间配置
     *
     * @param lockConfigBO 配置
     * @param request      请求参数
     * @param function     业务处理
     * @param <T>          参数类型
     * @param <R>          应答类型
     * @return 处理结果
     */
    public <T, R> R execute(RedissonLockWrapper lockConfigBO, T request, Function<T, R> function) {
        return doExecute(lockConfigBO, () -> function.apply(request));
    }

    /**
     * 加锁执行,指定时间配置
     *
     * @param lockConfigBO 配置
     * @param runnable     业务处理
     */
    public void execute(RedissonLockWrapper lockConfigBO, Runnable runnable) {
        doExecute(lockConfigBO, () -> {
            runnable.run();
            return RedissonConstant.SUCCESS;
        });
    }

    /**
     * 加锁执行,指定时间配置
     *
     * @param lockConfigBO 配置
     * @param callable     业务处理
     */
    public <T> T execute(RedissonLockWrapper lockConfigBO, Callable<T> callable) {
        return doExecute(lockConfigBO, callable);
    }

    private <T> T doExecute(RedissonLockWrapper lockConfigBO, Callable<T> callable) {
        RLock lock = redissonClient.getLock(lockConfigBO.getLockKey());
        try {
            tryLock(lock, lockConfigBO.getWaitTime(), lockConfigBO.getLeaseTime());
            return callable.call();
        } catch (Exception e) {
            log.error("执行异常", e);
            throw new ExceptionServer(ExceptionEnum.GET_LOCK_ERROR, e);
        } finally {
            tryUnlock(lock);
        }
    }

    private void tryLock(RLock lock, long waitTime, long leaseTime) throws InterruptedException {
        if (Objects.equals(waitTime, RedissonConstant.UNLIMITED_TIME) || Objects.equals(leaseTime, RedissonConstant.UNLIMITED_TIME)) {
            lock.lock();
            return;
        }
        boolean lockSuccess = lock.tryLock(waitTime, leaseTime, TimeUnit.MILLISECONDS);
        if (!lockSuccess) {
            throw new ExceptionServer(ExceptionEnum.GET_LOCK_ERROR);
        }
    }

    private void tryUnlock(RLock lock) {
        if (lock.isLocked() && lock.isHeldByCurrentThread()) {
            lock.unlock();
        }
    }
}
